﻿/*
 *  Copyright 2009 Coditate Software
 *  Licensed under the GNU Library General Public License (LGPL) 2.1 
 *  
 *  License available at: http://simplesavant.codeplex.com/license
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using Coditate.Common.Util;

namespace Coditate.Savant.Async
{
    /// <summary>
    /// Defines extension methods for calling Savant asynchronously.
    /// </summary>
    /// <remarks>
    /// See <see cref="SimpleSavant"/> for a usage example.
    /// </remarks>
    public static class AsyncExtensions
    {
        /// <summary>
        /// Executes the specified function.
        /// </summary>
        /// <typeparam name="R"></typeparam>
        /// <param name="savant">The savant instance on which to invoke the function.</param>
        /// <param name="function">The function to invoke.</param>
        /// <returns></returns>
        [Obsolete("Use the Begin* and End* extension methods rather than ExecuteAsync()")]
        public static AsyncFunction<R> ExecuteAsync<R>(this ISimpleSavant savant, Func<ISimpleSavant, R> function)
        {
            Arg.CheckNull("savant", savant);
            Arg.CheckNull("function", function);

            var result = new AsyncFunction<R>(function, savant);
            result.BeginInvoke();

            return result;
        }

        /// <summary>
        /// Executes the specified action.
        /// </summary>
        /// <param name="savant">The savant instance on which to invoke the action.</param>
        /// <param name="action">The action to invoke.</param>
        /// <returns></returns>
        [Obsolete("Use the Begin* and End* extension methods rather than ExecuteAsync()")]
        public static AsyncAction ExecuteAsync(this ISimpleSavant savant, Action<ISimpleSavant> action)
        {
            Arg.CheckNull("savant", savant);
            Arg.CheckNull("action", action);

            return ExecuteAsyncImpl(savant, action, null);
        }

        /// <summary>
        /// Executes the specified action.
        /// </summary>
        /// <param name="savant">The savant instance on which to invoke the action.</param>
        /// <param name="action">The action to invoke.</param>
        /// <param name="tracker">The action tracker.</param>
        /// <returns></returns>
        [Obsolete("Use the Begin* and End* extension methods rather than ExecuteAsync()")]
        public static AsyncAction ExecuteAsync(this ISimpleSavant savant, Action<ISimpleSavant> action,
                                               ref ActionTracker tracker)
        {
            Arg.CheckNull("savant", savant);
            Arg.CheckNull("action", action);

            if (tracker == null)
            {
                tracker = new ActionTracker();
            }

            return ExecuteAsyncImpl(savant, action, tracker);
        }

        private static AsyncAction ExecuteAsyncImpl(ISimpleSavant savant, Action<ISimpleSavant> action,
                                                    ActionTracker tracker)
        {
            var result = new AsyncAction(action, savant);
            result.BeginInvoke();

            if (tracker != null)
            {
                lock (((ICollection) tracker.Actions).SyncRoot)
                {
                    tracker.Actions.Add(result);
                }
            }

            return result;
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.Put"/>.
        /// </summary>
        public static IAsyncResult BeginPut(this ISimpleSavant savant, object[] items, AsyncCallback callback,
                                            object state)
        {
            return savant.BeginAction(s => s.Put(items), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.Put"/>.
        /// </summary>
        public static void EndPut(this ISimpleSavant savant, IAsyncResult result)
        {
            EndAction<ISimpleSavant>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.Get{T}"/>.
        /// </summary>
        public static IAsyncResult BeginGet<T>(this ISimpleSavant savant, object itemName, AsyncCallback callback,
                                               object state)
        {
            return savant.BeginFunction(s => s.Get<T>(itemName), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.Get{T}"/>.
        /// </summary>
        public static T EndGet<T>(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, T>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.GetAttributes{T}"/>.
        /// </summary>
        public static IAsyncResult BeginGetAttributes<T>(this ISimpleSavant savant, object itemName,
                                                         string[] propertyNames, AsyncCallback callback,
                                                         object state)
        {
            return savant.BeginFunction(s => s.GetAttributes<T>(itemName, propertyNames), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.GetAttributes{T}"/>.
        /// </summary>
        public static PropertyValues EndGetAttributes(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, PropertyValues>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant2.GetAttributes"/>.
        /// </summary>
        public static IAsyncResult BeginGetAttributes(this ISimpleSavant2 savant, ItemMapping mapping, object itemName,
                                                      string[] propertyNames, AsyncCallback callback,
                                                      object state)
        {
            return savant.BeginFunction(s => s.GetAttributes(mapping, itemName, propertyNames), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant2.GetAttributes"/>.
        /// </summary>
        public static PropertyValues EndGetAttributes(this ISimpleSavant2 savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant2, PropertyValues>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant2.DeleteAttributes"/>.
        /// </summary>
        public static IAsyncResult BeginDeleteAttributes(this ISimpleSavant2 savant, ItemMapping mapping,
                                                         object itemName,
                                                         string[] propertyNames, AsyncCallback callback,
                                                         object state)
        {
            return savant.BeginAction(s => s.DeleteAttributes(mapping, itemName, propertyNames), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant2.DeleteAttributes"/>.
        /// </summary>
        public static void EndDeleteAttributes(this ISimpleSavant2 savant, IAsyncResult result)
        {
            EndAction<ISimpleSavant2>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant2.SelectAttributes"/>.
        /// </summary>
        public static IAsyncResult BeginSelectAttributes(this ISimpleSavant2 savant, SelectCommand command,
                                                         AsyncCallback callback,
                                                         object state)
        {
            return savant.BeginFunction(s => s.SelectAttributes(command), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant2.SelectAttributes"/>.
        /// </summary>
        public static SelectResults<PropertyValues> EndSelectAttributes(this ISimpleSavant2 savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant2, SelectResults<PropertyValues>>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant2.SelectScalar"/>.
        /// </summary>
        public static IAsyncResult BeginSelectScalar(this ISimpleSavant2 savant, SelectCommand command,
                                                     AsyncCallback callback,
                                                     object state)
        {
            return savant.BeginFunction(s => s.SelectScalar(command), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant2.SelectScalar"/>.
        /// </summary>
        public static object EndSelectScalar(this ISimpleSavant2 savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant2, object>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.PutAttributes{T}"/>.
        /// </summary>
        public static IAsyncResult BeginPutAttributes<T>(this ISimpleSavant savant, PropertyValues[] values,
                                                         AsyncCallback callback,
                                                         object state)
        {
            return savant.BeginAction(s => s.PutAttributes<T>(values), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.PutAttributes{T}"/>.
        /// </summary>
        public static void EndPutAttributes(this ISimpleSavant savant, IAsyncResult result)
        {
            EndAction<ISimpleSavant>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant2.PutAttributes"/>.
        /// </summary>
        public static IAsyncResult BeginPutAttributes(this ISimpleSavant2 savant, ItemMapping mapping,
                                                      PropertyValues[] values,
                                                      AsyncCallback callback,
                                                      object state)
        {
            return savant.BeginAction(s => s.PutAttributes(mapping, values), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant2.PutAttributes"/>.
        /// </summary>
        public static void EndPutAttributes(this ISimpleSavant2 savant, IAsyncResult result)
        {
            EndAction<ISimpleSavant2>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.Delete{T}"/>.
        /// </summary>
        public static IAsyncResult BeginDelete<T>(this ISimpleSavant savant, object itemName,
                                                  AsyncCallback callback,
                                                  object state)
        {
            return savant.BeginAction(s => s.Delete<T>(itemName), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.Delete{T}"/>.
        /// </summary>
        public static void EndDelete(this ISimpleSavant savant, IAsyncResult result)
        {
            EndAction<ISimpleSavant>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.DeleteAttributes{T}"/>.
        /// </summary>
        public static IAsyncResult BeginDeleteAttributes<T>(this ISimpleSavant savant, object itemName,
                                                            string[] propertyNames,
                                                            AsyncCallback callback,
                                                            object state)
        {
            return savant.BeginAction(s => s.DeleteAttributes<T>(itemName, propertyNames), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.DeleteAttributes{T}"/>.
        /// </summary>
        public static void EndDeleteAttributes(this ISimpleSavant savant, IAsyncResult result)
        {
            EndAction<ISimpleSavant>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.SelectAttributes{T}(SelectCommand{T})"/>.
        /// </summary>
        public static IAsyncResult BeginSelectAttributes<T>(this ISimpleSavant savant, SelectCommand<T> command,
                                                            AsyncCallback callback,
                                                            object state)
        {
            return savant.BeginFunction(s => s.SelectAttributes(command), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.SelectAttributes{T}(SelectCommand{T})"/>.
        /// </summary>
        public static SelectResults<PropertyValues> EndSelectAttributes(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, SelectResults<PropertyValues>>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.Select{T}(SelectCommand{T})"/>.
        /// </summary>
        public static IAsyncResult BeginSelect<T>(this ISimpleSavant savant, SelectCommand<T> command,
                                                  AsyncCallback callback,
                                                  object state)
        {
            return savant.BeginFunction(s => s.Select(command), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.Select{T}(SelectCommand{T})"/>.
        /// </summary>
        public static SelectResults<T> EndSelect<T>(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, SelectResults<T>>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.SelectScalar{T}(string, CommandParameter[])"/>.
        /// </summary>
        public static IAsyncResult BeginSelectScalar<T>(this ISimpleSavant savant, string selectStatement,
                                                        CommandParameter[] selectParams,
                                                        AsyncCallback callback,
                                                        object state)
        {
            return savant.BeginFunction(s => s.SelectScalar<T>(selectStatement, selectParams), callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.SelectScalar{T}(string, CommandParameter[])"/>.
        /// </summary>
        public static object EndSelectScalar(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, object>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.Find{T}"/>.
        /// </summary>
        public static IAsyncResult BeginFind<T>(this ISimpleSavant savant, string queryText, int resultStartIndex,
                                                int resultCount, string searchProperty,
                                                AsyncCallback callback,
                                                object state)
        {
            return savant.BeginFunction(s => s.Find<T>(queryText, resultStartIndex, resultCount, searchProperty),
                                        callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.Find{T}"/>.
        /// </summary>
        public static List<T> EndFind<T>(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, List<T>>(result);
        }

        /// <summary>
        /// Begin asynchronous invocation of <see cref="ISimpleSavant.FindAttributes{T}"/>.
        /// </summary>
        public static IAsyncResult BeginFindAttributes<T>(this ISimpleSavant savant, string queryText,
                                                          int resultStartIndex, int resultCount, string searchProperty,
                                                          string[] propertyNames,
                                                          AsyncCallback callback,
                                                          object state)
        {
            return
                savant.BeginFunction(
                    s => s.FindAttributes<T>(queryText, resultStartIndex, resultCount, searchProperty, propertyNames),
                    callback, state);
        }

        /// <summary>
        /// End asynchronous invocation of <see cref="ISimpleSavant.FindAttributes{T}"/>.
        /// </summary>
        public static List<PropertyValues> EndFindAttributes(this ISimpleSavant savant, IAsyncResult result)
        {
            return EndFunction<ISimpleSavant, List<PropertyValues>>(result);
        }

        private static IAsyncResult BeginAction<T>(this T savant, Action<T> action,
                                                   AsyncCallback callback,
                                                   object state)
        {
            return action.BeginInvoke(savant, callback, state);
        }

        private static IAsyncResult BeginFunction<T, R>(this T savant, Func<T, R> func,
                                                        AsyncCallback callback,
                                                        object state)
        {
            return func.BeginInvoke(savant, callback, state);
        }

        private static void EndAction<T>(IAsyncResult result)
        {
            var resultImpl = (AsyncResult) result;
            var action = resultImpl.AsyncDelegate as Action<T>;
            if (action == null)
            {
                throw new InvalidOperationException(
                    "The provided IAsyncResult was returned from a Begin method that does not match the current End method.");
            }
            action.EndInvoke(result);
        }

        private static R EndFunction<T, R>(IAsyncResult result)
        {
            var resultImpl = (AsyncResult) result;
            var func = resultImpl.AsyncDelegate as Func<T, R>;
            if (func == null)
            {
                throw new InvalidOperationException(
                    "The provided IAsyncResult was returned from a Begin method that does not match the current End method.");
            }
            return func.EndInvoke(result);
        }
    }
}